// !函数兼容性
// #函数参数个数
// 函数参数个数如果要兼容，需要满足一个要求：如果对函数 y 进行赋值，那么要求 x 中的每个参数都应在 y 中有对应，也就是 x 的参数个数小于等于 y 的参数个数
let x = (a: number) => 0;
let y = (b: number, c: string) => 0;
y = x;
// x = y; //不能将类型“(b: number, c: string) => number”分配给类型“(a: number) => number”。

// 传给 forEach 的回调函数的参数是三个，但是可以只用一个，这样就只需写一个参数。我们传入的 forEach 的函数是 forEach 的参数，它是一个函数，这个函数的参数列表是定义在 forEach 方法内的，我们可以传入一个参数少于等于参数列表的函数，但是不能传入一个比参数列表参数个数还多的函数
const arrMath = [1, 2, 3];
arrMath.forEach((item, index, array) => {
  console.log(item); //1 2 3
});
arrMath.forEach((item) => {
  console.log(item); //1 2 3
});

// #函数参数类型
let x1 = (a: number) => 0;
let y1 = (b: string) => 0;
let z1 = (c: string) => false;
// x1 = y1; // error 不能将类型“(b: string) => number”分配给类型“(a: number) => number”。
// x1 = z1; // error 不能将类型“(c: string) => boolean”分配给类型“(a: number) => number”。
// y1 = z1;  //error 不能将类型“(c: string) => boolean”分配给类型“(b: string) => number”。

// #剩余参数和可选参数
// 当要被赋值的函数参数中包含剩余参数（…args）时，赋值的函数可以用任意个数参数代替，但是类型需要对应
const getNum = (
  // 这里定义一个getNum函数，他有两个参数
  arr: number[], // 第一个参数是一个数组
  callback: (...args: number[]) => number // 第二个参数是一个函数，这个函数的类型要求可以传入任意多个参数，但是类型必须是数值类型，返回值必须是数值类型
): number => {
  return callback(...arr); // 这个getNum函数直接返回调用传入的第二个参数这个函数，以第一个参数这个数组作为参数的函数返回值
};
getNum(
  [1, 2],
  (...args: number[]): number => args.length // 这里传入一个函数，逻辑是返回参数的个数
);

// #函数参数双向协变
// 函数参数双向协变即参数类型无需绝对相同
// funcA 和 funcB 的参数类型并不完全一样，funcA 的参数类型为一个联合类型 number | string，而 funcB 的参数类型为 number | string 中的 number，他们两个函数也是兼容的
let funcA = function (arg: number | string): void {};
let funcB = function (arg: number): void {};
// funcA = funcB 和 funcB = funcA都可以

// #函数返回值类型
let x2 = (a: number): string | number => 0;
let y2 = (b: number) => "a";
let z2 = (c: number) => false;
x2 = y2;
// x2 = z2; // 不能将类型“(c: number) => boolean”分配给类型“(a: number) => string | number”

// #函数重载
// 带有重载的函数，要求被赋值的函数的每个重载都能在用来赋值的函数上找到对应的签名
function merge1(arg1: number, arg2: number): number; // 这是merge函数重载的一部分
function merge1(arg1: string, arg2: string): string; // 这也是merge函数重载的一部分
function merge1(arg1: any, arg2: any) {
  // 这是merge函数实体
  return arg1 + arg2;
}
function sum(arg1: number, arg2: number): number; // 这是sum函数重载的一部分
function sum(arg1: any, arg2: any): any {
  // 这是sum函数实体
  return arg1 + arg2;
}
let func = merge1;
// func = sum; // error 不能将类型“(arg1: number, arg2: number) => number”分配给类型“{ (arg1: number, arg2: number): number; (arg1: string, arg2: string): string; }”

// !枚举
// 数字枚举成员类型与数字类型互相兼容
enum Status {
  On,
  Off,
}
let s = Status.On;
s = 1;
// s = 3;

// 但是不同枚举值之间是不兼容的
enum Status2 {
  On,
  Off,
}
enum Color {
  White,
  Black,
}
let s2 = Status2.On;
// s2 = Color.White; // error Type 'Color.White' is not assignable to type 'Status'

// 字符串枚举成员类型和字符串类型是不兼容的
enum Status3 {
  On1 = "on",
  Off1 = "off",
}
let s3 = Status3.On1;
// s3 = 'Lison' // error 不能将类型“"Lison"”分配给类型“Status”

// !类
// 比较两个类类型的值的兼容性时，只比较实例的成员，类的静态成员和构造函数不进行比较
class Animal1 {
  static age: number;
  constructor(public name: string) {}
}
class People1 {
  static age: string;
  constructor(public name: string) {}
}
class Food1 {
  constructor(public name: number) {}
}
let a3: Animal1;
let p3: People1;
let f3: Food1;
// a3 = p3; // error 在赋值前使用了变量“p3”。
// a3 = f3; // error 不能将类型“Food1”分配给类型“Animal1”。//不能将类型“number”分配给类型“string”。

// !泛型
// 泛型包含类型参数，这个类型参数可能是任意类型，使用时类型参数会被指定为特定的类型，而这个类型只影响使用了类型参数的部分
interface Data<T> {}
let data1: Data<number>;
let data2: Data<string>;

// data1 = data2; //在赋值前使用了变量“data2”。

interface Data<T> {
  data: T;
}
let data3: Data<number>;
let data4: Data<string>;

// data3 = data4; // error 不能将类型“Data<string>”分配给类型“Data<number>”。不能将类型“string”分配给类型“number”
